package com.xinzhitong.www.service.impl;

import com.xinzhitong.www.enumeration.RoleType;
import com.xinzhitong.www.exception.*;
import com.xinzhitong.www.exception.certification.IncorrectCertificationException;
import com.xinzhitong.www.exception.checkcode.WrongCheckCodeException;
import com.xinzhitong.www.exception.email.InvalidEmailException;
import com.xinzhitong.www.exception.email.NotRegisteredEmailException;
import com.xinzhitong.www.exception.personalinfo.*;
import com.xinzhitong.www.exception.phone.MultiBoundPhoneException;
import com.xinzhitong.www.exception.phone.NotBoundPhoneException;
import com.xinzhitong.www.exception.register.PasswordIsNotStandardException;
import com.xinzhitong.www.exception.register.UsernameExistException;
import com.xinzhitong.www.exception.register.UsernameIsNotStandardException;
import com.xinzhitong.www.mapper.SystemMessageMapper;
import com.xinzhitong.www.mapper.UserMapper;
import com.xinzhitong.www.pojo.TempAccount;
import com.xinzhitong.www.pojo.User;
import com.xinzhitong.www.service.UserService;
import com.xinzhitong.www.utils.EmailUtils;
import com.xinzhitong.www.utils.FileHelper;
import com.xinzhitong.www.utils.Md5SaltTool;
import com.xinzhitong.www.utils.SMSUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

/**
 * 账户服务实现类
 *
 * @author 徐鑫
 */
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Resource
    SystemMessageMapper systemMessageMapper;

    /**
     * 用户信息存取路径
     */
    private final String USERSPATH = "/static/user";

    /**
     * 未激活的注册邮箱账号缓存
     */
    private static Cache<String, TempAccount> registerEmailAccountCache;

    /**
     * 未确认的邮箱密码修改
     */
    private static Cache<String, TempAccount> resetPasswordOfEmailAccountCache;

    /**
     * 接口map
     */
    private static Map<EmailUtils.EmailType, String> urlMapper = new HashMap<>();

    static {
        //初始化激活的注册邮箱账号缓存
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:EmailAccount-ehcache-setting.xml");
        registerEmailAccountCache = ehCacheManager.getCache("registerEmailAccountCache");
        resetPasswordOfEmailAccountCache = ehCacheManager.getCache("resetPasswordOfEmailAccountCache");

        urlMapper.put(EmailUtils.EmailType.REGISTER, "/user/emailAccountActive");
        urlMapper.put(EmailUtils.EmailType.RESETPASSWORD, "/user/emailAccountPasswordReset");
        urlMapper.put(EmailUtils.EmailType.LOGIN, "/--");
    }

    @Resource
    UserMapper userMapper;

    @Override
    public void login(User user) throws AccountException {
        Subject subject = SecurityUtils.getSubject();
        subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
        //记录登录时间，用于退出时记录
        ((User) subject.getPrincipal()).setLastLoginTime(
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())
        );
    }

    @Override
    public void login(String phone, String checkCode) throws CheckCodeException, AccountException {
        //验证码不正确
        if (SMSUtil.wrongSMSCode(phone, checkCode, SMSUtil.SMSCodeType.LOGIN))
            throw new WrongCheckCodeException();
        //正确
        User user = userMapper.selectByUsername(phone);
        Subject subject = SecurityUtils.getSubject();
        subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
        subject = SecurityUtils.getSubject();
        subject.getSession().setTimeout(30 * 24 * 60 * 60);
    }

    @Override
    public void register(User user) throws RegisterException, BusyException {
        //尝试获取同名用户
        if (userMapper.selectByUsername(user.getUsername()) != null)
            throw new UsernameExistException();
        else if (passwordIsNotStandard(user.getPassword())) {
            throw new PasswordIsNotStandardException();
        } else if (usernameIsNotStandard(user.getUsername())) {
            throw new UsernameIsNotStandardException();
        } else {
            try {
                //密码加密
                user.setPassword(Md5SaltTool.getEncryptedPwd(user.getPassword()));
                Integer res = userMapper.insertUser(user);
                if (res == 0)
                    throw new BusyException("注册失败");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                throw new BusyException();
            }
        }
    }

    @Override
    public void register(String phone, String password, String checkCode) throws RegisterException, BusyException, CheckCodeException {
        if (SMSUtil.wrongSMSCode(phone, checkCode, SMSUtil.SMSCodeType.REGISTER))
            throw new WrongCheckCodeException();
        //验证码正确
        User user = new User();
        user.setUsername(phone);
        try {
            user.setPassword(Md5SaltTool.getEncryptedPwd(password));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new BusyException();
        }
        if (userMapper.insertUser(user) == 0)
            throw new BusyException();
    }

    @Override
    public void resetPassword(String phone, String password, String checkCode) throws BusyException, CheckCodeException {
        if (SMSUtil.wrongSMSCode(phone, checkCode, SMSUtil.SMSCodeType.RESETPASSWORD))
            throw new WrongCheckCodeException();
        //验证码正确
        User user = userMapper.selectByUsername(phone);
        try {
            user.setPassword(Md5SaltTool.getEncryptedPwd(password));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new BusyException();
        }
        //更新密码
        if (userMapper.updateUser("user", user, "username='" + user.getUsername() + "'") == 0)
            throw new BusyException();

        //退出
        Subject subject = SecurityUtils.getSubject();
        if (subject != null)
            subject.logout();
    }


    @Override
    public Map<String, Object> getEnrollInfo() throws PersonalInfoException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        user = userMapper.selectByUsername(user.getUsername());
        if (user.getRealname() == null)
            throw new RealNameIsNotExistException();
        if (user.getId() == null)
            throw new IdIsNotExistException();
        if (user.getContact() == null)
            throw new ContactIsNotExistException();
        info.put("realname", user.getRealname());
        info.put("id", user.getId());
        info.put("contact", user.getContact());
        return info;
    }

    @Override
    public Map<String, Object> getMainInfo() throws PersonalInfoException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        user = userMapper.selectByUsername(user.getUsername());
        if (user == null)
            throw new PersonInfoInvalidException();
        info.put("head", FileHelper.fileURL + user.getHead());
        info.put("sex", user.getSex());
        info.put("nickname", user.getNickname());
        info.put("username", user.getUsername());
        info.put("realname", user.getRealname());
        info.put("hasQualification", user.getId() != null);
        info.put("province", user.getProvince());
        info.put("city", user.getCity());
        info.put("area", user.getArea());
        info.put("birthday", user.getBirthday());
        info.put("occupation", user.getOccupation());
        return info;
    }

    @Override
    public void editUserInfo(MultipartFile head, User newInfo) throws IOException {
        User user = userMapper.selectByUsername(((User) SecurityUtils.getSubject().getPrincipal()).getUsername());

        //重复设置性别
        if (user.getSex() != null && newInfo.getSex() != null && user.getSex() != newInfo.getSex())
            throw new MultipleSexSettingException();

        if (head != null) {
            //存入头像
            String path = USERSPATH + "/" + user.getUsername() + "/head.jpg";
            FileHelper.saveTo(path, head);
            newInfo.setHead(path);
        }

        //将user信息通过反射进行修改
        Class cls = user.getClass();
        for (Field field : cls.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                //忽略新信息中为null的字段
                if (field.get(newInfo) != null)
                    field.set(user, field.get(newInfo));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //在数据库中更新用户
        Integer res = userMapper.updateUser("user", user, "username='" + user.getUsername() + "'");
        if (res == 0)
            throw new BusyException();
    }

    @Override
    //test
    public HashMap<String, Object> editUserInfoTest(Integer head, User newInfo) throws Exception {
        User user = userMapper.selectByUsername(((User) SecurityUtils.getSubject().getPrincipal()).getUsername());

        //重复设置性别
        if (user.getSex() != null && newInfo.getSex() != null && user.getSex() != newInfo.getSex())
            throw new MultipleSexSettingException();

        if (head != 0) {
            //存入头像
            String path = USERSPATH + "/" + user.getUsername() ;
            //FileHelper.saveTo(path, head);
            String fileName = "/head.jpg";
            HashMap<String, Object> re = FileHelper.generateSign(null,path,fileName);
            newInfo.setHead(path);
            return re;
        }

        //将user信息通过反射进行修改
        Class cls = user.getClass();
        for (Field field : cls.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                //忽略新信息中为null的字段
                if (field.get(newInfo) != null)
                    field.set(user, field.get(newInfo));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //在数据库中更新用户
        Integer res = userMapper.updateUser("user", user, "username='" + user.getUsername() + "'");
        if (res == 0)
            throw new BusyException();
        return null;
    }

    @Override
    public Map<String, Object> getMainInfoNew() throws PersonalInfoException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        user = userMapper.selectByUsername(user.getUsername());
        if (user == null)
            throw new PersonInfoInvalidException();
        info.put("headNum", user.getHead());
        info.put("sex", user.getSex());
        info.put("nickname", user.getNickname());
        info.put("username", user.getUsername());
        info.put("realname", user.getRealname());
        info.put("hasQualification", user.getId() != null);
        info.put("province", user.getProvince());
        info.put("city", user.getCity());
        info.put("area", user.getArea());
        info.put("birthday", user.getBirthday());
        info.put("occupation", user.getOccupation());
        return info;
    }

    @Override
    public void editUserInfo(Integer headNum, User newInfo) throws IOException {
        User user = userMapper.selectByUsername(((User) SecurityUtils.getSubject().getPrincipal()).getUsername());

        //重复设置性别
        if (user.getSex() != null && newInfo.getSex() != null && user.getSex() != newInfo.getSex())
            throw new MultipleSexSettingException();

        if (headNum != 0) {
            //存入头像
            String path = USERSPATH + "/" + user.getUsername() + "/head.jpg";
            newInfo.setHead(path);
        }

        //将user信息通过反射进行修改
        Class cls = user.getClass();
        for (Field field : cls.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                //忽略新信息中为null的字段
                if (field.get(newInfo) != null)
                    field.set(user, field.get(newInfo));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //在数据库中更新用户
        Integer res = userMapper.updateUser("user", user, "username='" + user.getUsername() + "'");
        if (res == 0)
            throw new BusyException();
    }

    @Override
    public void sendSMS(String phone, SMSUtil.SMSCodeType smsCodeType) throws CheckCodeException {
        User user;
        switch (smsCodeType) {
            case REGISTER:
                //注册需要用户不存在
                user = userMapper.selectByUsername(phone);
                if (user != null)
                    throw new MultiBoundPhoneException();
                break;
            case RESETPASSWORD:
            case LOGIN:
                //登录与重置密码需要用户存在
                user = userMapper.selectByUsername(phone);
                if (user == null)
                    throw new NotBoundPhoneException();
        }
        SMSUtil.sendSMSCode(phone, smsCodeType);
    }

    @Override
    public void recordCertification(
            MultipartFile front,
            MultipartFile behind,
            String realname,
            String id
    ) throws CertificationException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        if (!validCertification(front, behind, realname, id))
            throw new IncorrectCertificationException();

        //添加身份证正面背面照
        String frontPath = USERSPATH + "/" + user.getUsername() + "/" + "front.jpg";
        String behindPath = USERSPATH + "/" + user.getUsername() + "/" + "behind.jpg";
        try {
            FileHelper.saveTo(frontPath, front);
            FileHelper.saveTo(behindPath, behind);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BusyException();
        }

        user.setRealname(realname);
        user.setId(id);
        user.setIdCardFront(frontPath);
        user.setIdCardBehind(behindPath);

        Integer res = userMapper.updateUser("user", user, "username='" + user.getUsername() + "'");
        if (res == 0)
            throw new BusyException();
    }

    @Override
    public Boolean ifUserAuthentication() {
        Subject subject = SecurityUtils.getSubject();
        User userName = (User) subject.getPrincipal();
        User user = userMapper.selectByUsername(userName.getUsername());
        if (user.getId() != null && user.getIdCardBehind() != null && user.getIdCardFront() != null) {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    @Override
    public void recordUsersLastLoginTime() {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        //更新上次登录时间为当前时间
        userMapper.updateLastLoginTime(user);
    }

    @Override
    public synchronized void sendEmail(
            TempAccount tempAccount,
            EmailUtils.EmailType emailType,
            RoleType roleType
    ) {
        //校验
        switch (emailType) {
            case RESETPASSWORD:
                if (userMapper.selectByUsername(tempAccount.getUsername()) == null)
                    throw new NotRegisteredEmailException();
                break;
            case REGISTER:
                if (userMapper.selectByUsername(tempAccount.getUsername()) != null)
                    throw new UsernameExistException();
                break;
        }

        if (passwordIsNotStandard(tempAccount.getPassword()))
            throw new PasswordIsNotStandardException();

        tempAccount.setRole(roleType);
        //随机生成密钥
        String randomKey;
        randomKey = getRandomKeyOfEmailAccount(emailType, tempAccount);
        String activeURL =
                FileHelper.serverURL +
                        urlMapper.get(emailType) + "?randomKey=" + randomKey;
        try {
            tempAccount.setPassword(Md5SaltTool.getEncryptedPwd(tempAccount.getPassword()));
            //发送邮件
            EmailUtils.sendEmail(
                    tempAccount.getUsername(),
                    EmailUtils.getText(emailType, roleType, activeURL),
                    EmailUtils.getSubject(emailType, roleType)
            );
        } catch (MessagingException e) {
            e.printStackTrace();
            //移除账号缓存
            switch (emailType) {
                case REGISTER:
                    registerEmailAccountCache.remove(randomKey);
                    break;
                case RESETPASSWORD:
                    resetPasswordOfEmailAccountCache.remove(randomKey);
                    break;
            }
            throw new InvalidEmailException();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new BusyException();
        }
    }

    /**
     * 添加缓存并获取对应的随机密钥
     *
     * @param emailType   邮件类型
     * @param tempAccount 临时账户
     * @return 随机密钥
     */
    private String getRandomKeyOfEmailAccount(EmailUtils.EmailType emailType, TempAccount tempAccount) {
        StringBuilder randomKey = new StringBuilder(new Random().nextInt(100000000));
        String username = tempAccount.getUsername();
        switch (emailType) {
            case REGISTER:
                for (char i : username.toCharArray())
                    randomKey.append(i - 1);
                //添加缓存
                registerEmailAccountCache.put(randomKey.toString(), tempAccount);
                break;
            case RESETPASSWORD:
                for (char i : username.toCharArray())
                    randomKey.append(i - 1);
                //添加缓存
                resetPasswordOfEmailAccountCache.put(randomKey.toString(), tempAccount);
                break;
        }
        return randomKey.toString();
    }

    @Override
    public void activeEmailAccount(String randomKey) {
        TempAccount tempAccount = registerEmailAccountCache.get(randomKey);
        if (tempAccount == null)
            throw new NullPointerException();
        User user = new User();
        user.setUsername(tempAccount.getUsername());
        user.setPassword(tempAccount.getPassword());
        user.setRole(tempAccount.getRole());
        if (userMapper.insertUserWithRole(user) == 0)
            throw new BusyException();
        registerEmailAccountCache.remove(randomKey);
        systemMessageMapper.insertSystemMessage(user.getUsername(), "欢迎加盟新职通", "欢迎", "");
    }

    @Override
    public void resetEmailAccountPassword(String randomKey) {
        TempAccount tempAccount = resetPasswordOfEmailAccountCache.get(randomKey);
        if (tempAccount == null)
            throw new NullPointerException();
        if (userMapper.updateUserByTempAccount(tempAccount) == 0)
            throw new BusyException();
        resetPasswordOfEmailAccountCache.remove(randomKey);
    }

    @Override
    public void resetPasswordByOldOne(String oldOne, String newOne) {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        user = userMapper.selectByUsername(user.getUsername());
        if (!Md5SaltTool.validPassword(oldOne, user.getPassword()))
            throw new IncorrectCredentialsException();
        try {
            user.setPassword(Md5SaltTool.getEncryptedPwd(newOne));
            userMapper.updateUser("user", user, "username='" + user.getUsername() + "'");
            subject.logout();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new BusyException();
        }
    }

    /**
     * 检验密码是否符合标准
     *
     * @param password 密码
     * @return 是否符合标准
     */
    private boolean passwordIsNotStandard(String password) {
        // TODO 检验密码
        return false;
    }

    /**
     * 校验用户名是否符合标准
     *
     * @param username 用户名
     * @return 是否符合标准
     */
    private boolean usernameIsNotStandard(String username) {
        // TODO 校验用户名
        return false;
    }

    /**
     * 进行实名认证
     *
     * @param front    身份证正面
     * @param behind   身份证背面
     * @param realname 真实姓名
     * @param id       身份证号码
     * @return 是否认证成功
     */
    private boolean validCertification(
            MultipartFile front,
            MultipartFile behind,
            String realname,
            String id
    ) {
        // TODO 实名认证
        return true;
    }
}
